#ifndef __BLOCK_VECTOR_H__
#define __BLOCK_VECTOR_H__

#include "block_types.h"
#include "static_assert.h"
#include "block_vector_impl.h"

template<typename T, int N>
class Vector_T
{
    template<typename U, int M>
    friend class Vector_T;

    typedef T ArrayType[N];

    ArrayType       vec_;

public:
    typedef T                       value_type;

    typedef T*                      iterator;
    typedef const T*                const_iterator;


    typedef value_type*             pointer;
    typedef value_type const *      const_pointer;

    typedef T&                      reference;
    typedef const T&                const_reference;

    typedef std::size_t             size_type;
    typedef std::ptrdiff_t          difference_type;

    enum { elem_num = N };

public:
    Vector_T()  {}

    explicit Vector_T(T const * rhs)
    {
        vector_impl<T, N>::DoCopy(&vec_[0], rhs);
    }

    explicit Vector_T(T const & rhs)
    {
        vector_impl<T, N>::DoAssign(&vec_[0], rhs);
    }

    Vector_T(Vector_T const & rhs)
    {
        vector_impl<T, N>::DoCopy(&vec_[0], &rhs[0]);
    }

    template<typename U, int M>
    Vector_T(Vector_T<U, M> const & rhs)
    {
        CORE_STATIC_ASSERT(M >= N);

        vector_impl<T, N>::DoCopy(&vec_[0], &rhs[0]);
    }

    Vector_T(T const & x, T const & y)
    {
        CORE_STATIC_ASSERT(2 == elem_num);

        this->x() = x;
        this->y() = y;
    }

    Vector_T(T const & x, T const & y, T const & z)
    {
        CORE_STATIC_ASSERT(3 == elem_num);

        this->x() = x;
        this->y() = y;
        this->z() = z;
    }

    Vector_T(T const & x, T const & y, T const & z, T const & w)
    {
        CORE_STATIC_ASSERT(4 == elem_num);

        this->x() = x;
        this->y() = y;
        this->z() = z;
        this->w() = w;
    }

    static size_t size()
    {
        return elem_num;
    }

    static Vector_T const & zero()
    {
        static Vector_T<T, N> const z(T(0));

        return z;
    }

    iterator begin()
    {
        return vec_;
    }

    const_iterator begin() const
    {
        return vec_;
    }

    iterator end()
    {
        return vec_ + N;
    }

    const_iterator end() const
    {
        return vec_ + N;
    }

    reference operator[](size_t index)
    {
        return vec_[index];
    }

    const_reference operator[](size_t index) const
    {
        return vec_[index];
    }

    reference x()
    {
        CORE_STATIC_ASSERT(elem_num >= 1);
        return vec_[0];
    }

    const_reference x() const
    {
        CORE_STATIC_ASSERT(elem_num >= 1);
        return vec_[0];
    }

    reference y()
    {
        CORE_STATIC_ASSERT(elem_num >= 2);
        return vec_[1];
    }

    const_reference y() const
    {
        CORE_STATIC_ASSERT(elem_num >= 2);
        return vec_[1];
    }

    reference z()
    {
        CORE_STATIC_ASSERT(elem_num >= 3);
        return vec_[2];
    }

    const_reference z() const
    {
        CORE_STATIC_ASSERT(elem_num >= 3);
        return vec_[2];
    }

    reference w()
    {
        CORE_STATIC_ASSERT(elem_num >= 4);
        return vec_[3];        
    }

    const_reference w() const
    {
        CORE_STATIC_ASSERT(elem_num >= 4);
        return vec_[3];        
    }

    template<typename U>
    Vector_T const & operator+=(Vector_T<U, N> const & rhs)
    {
        vector_impl<T, N>::DoAdd(&vec_[0], &vec_[0], &rhs.vec_[0]);
        return *this;
    }

    friend Vector_T<T, N> operator+(Vector_T<T, N> const & lhs, Vector_T<T, N> const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv += rhs; return nrv;
    }

    template<typename U>
    Vector_T const & operator+=(U const & rhs)
    {
        vector_impl<T, N>::DoAdd(&vec_[0], &vec_[0], rhs);
        return *this;
    }

    friend Vector_T<T, N> operator+(Vector_T<T, N> const & lhs, T const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv += rhs; return nrv;
    }

    friend Vector_T<T, N> operator+(T const & lhs, Vector_T<T, N> const & rhs)
    {
        Vector_T<T, N> nrv(rhs); nrv += lhs; return nrv;
    }

    template<typename U>
    Vector_T const & operator-=(Vector_T<U, N> const & rhs)
    {
        vector_impl<T, N>::DoSub(&vec_[0], &vec_[0], &rhs.vec_[0]);
        return *this;
    }

    friend Vector_T<T, N> operator-(Vector_T<T, N> const & lhs , Vector_T<T, N> const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv -= rhs; return nrv;
    }

    template<typename U>
    Vector_T const & operator-=(U const & rhs)
    {
        vector_impl<T, N>::DoSub(&vec_[0], &vec_[0], rhs);
        return *this;
    }

    friend Vector_T<T, N> operator-(Vector_T<T, N> const & lhs, T const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv -= rhs; return nrv;
    }

//    friend Vector_T<T, N> operator-(T const & lhs, Vector_T<T, N> const & rhs)
//    {
//        return Vector_T<T, N>( lhs ) -= rhs;
//    }

    template<typename U>
    Vector_T const & operator*=(Vector_T<U, N> const & rhs)
    {
        vector_impl<T, N>::DoMul(&vec_[0], &vec_[0], &rhs.vec_[0]);
        return *this;
    }

    friend Vector_T<T, N> operator*(Vector_T<T, N> const & lhs, Vector_T<T, N> const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv *= rhs; return nrv;
    }

    template<typename U>
    Vector_T const & operator*=(U const & rhs)
    {
        vector_impl<T, N>::DoScale(&vec_[0], &vec_[0], rhs);
        return *this;
    }

    friend Vector_T<T, N> operator*(Vector_T<T, N> const & lhs, T const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv *= rhs; return nrv;
    }

    friend Vector_T<T, N> operator*(T const & lhs, Vector_T<T, N> const & rhs)
    {
        Vector_T<T, N> nrv(rhs); nrv *= lhs; return nrv;
    }

    template<typename U>
    Vector_T const & operator/=(Vector_T<U, N> const & rhs)
    {
        return this->operator*=(1.0f / rhs);
    }

    friend Vector_T<T, N> operator/(Vector_T<T, N> const & lhs, Vector_T<T, N> const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv /= rhs; return nrv;
    }

    template<typename U>
    Vector_T const & operator/=(U const & rhs)
    {
        return this->operator*=(1.0f / rhs);
    }

    friend Vector_T<T, N> operator/(Vector_T<T, N> const & lhs, T const & rhs)
    {
        Vector_T<T, N> nrv(lhs); nrv /= rhs; return nrv;
    }

//    friend Vector_T<T, N> operator/(T const & lhs, Vector_T<T, N> const & rhs)
//    {
//        return Vector_T<T, N> ( lhs ) /= rhs;
//    }

    Vector_T& operator=(Vector_T const & rhs)
    {
        if (this != &rhs)
        {
            std::copy(rhs.begin(), rhs.end(), begin());
        }
        return *this;
    }

    template<typename U, int M>
    Vector_T& operator=(Vector_T<U, M> const & rhs)
    {
        CORE_STATIC_ASSERT(M >= N);
        vector_impl<T, N>::DoCopy(&vec_[0], &rhs.vec_[0]);
        return *this;
    }

    Vector_T const operator+() const
    {
        return *this;
    }

    Vector_T const operator-() const
    {
        Vector_T tmp(*this);
        vector_impl<T, N>::DoNegate(&tmp.vec_[0], &vec_[0]);
        return tmp;
    }

    void swap(Vector_T & rhs)
    {
        vector_impl<T, N>::DoSwap(&vec_[0], &rhs.vec_[0]);
    }

    bool operator==(Vector_T const & rhs) const
    {
        return vector_impl<T, N>::DoEqual(&vec_[0], &rhs.vec_[0]);
    }
};

template<typename T, int N>
inline void swap(Vector_T<T, N> & lhs, Vector_T<T, N> & rhs)
{
    lhs.swap(rhs);
}




#endif
